﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PrattParsing;
using System.Diagnostics;

namespace sapphirelang.Parsing
{
    [DebuggerNonUserCode]
    class Scanner : Lexer
    {
        Queue<Token> tokenQueue;

        [DebuggerNonUserCode]
        public Scanner(string code)
            : base(code)
        {
            this.tokenQueue = new Queue<Token>();
        }

        [DebuggerNonUserCode]
        bool opchar(char c)
        {
            // not opchars (parsed seperately): () {} []
            // removed ':' and '@' from opchar list
            const string opchars = "~!#$%^&*-+=`|/?<>,.";
            return opchars.Contains(c);
        }
        [DebuggerNonUserCode]
        bool groupingchar(char c)
        {
            const string grouping = "()[]{}";
            return grouping.Contains(c);
        }

        bool identStart(char c)
        {
            return cur == '_' || char.IsLetter(cur);
        }
        bool identChar(char c)
        {
            return cur == '_' || char.IsLetterOrDigit(cur);
        }
        //bool identEnd(char c)
        //{
        //    return identChar(c) || c == '?';
        //}

        public override Token getToken()
        {
            if (tokenQueue.Count != 0)
                return tokenQueue.Dequeue();

            #region Whitespace
            while (whitespace(cur)) next();
            //bool isNewline = false;
            //while (whitespace(cur))
            //{
            //    if (cur == '\n' && !isNewline)
            //    {
            //        isNewline = true;
            //        setPos();
            //    }
            //    next();
            //}
            //if (isNewline) return newline();
            #endregion

            if (cur == '\0')
                return Token.EOF(makePos(1));
            else if (cur == ';')
            {
                #region Semicolon
                setPos();
                next();
                return semicolon();
                #endregion
            }
            else if (groupingchar(cur))
            {
                #region Grouping Character
                setPos();
                char grouping = cur;
                next();
                return groupsym("" + grouping);
                #endregion
            }
            else if (identStart(cur))
            {
                #region Identifier
                setPos();
                string name = "" + cur;
                next();
                while (identChar(cur))
                {
                    name += cur;
                    next();
                }
                return ident(name);
                #endregion
            }
            else if (opchar(cur))
            {
                #region Operator (and comments)
                setPos();
                string oper = "" + cur;
                next();
                while (opchar(cur))
                {
                    oper += cur;
                    next();

                    // comments!
                    #region Comments
                    if (oper.EndsWith("//"))
                    {
                        while (cur != '\n' && cur != '\0')
                            next();
                        if (oper == "//")
                            return getToken();
                        return op(oper.Remove(oper.Length - 1));
                    }
                    else if (oper.EndsWith("/*"))
                    {
                        string comment = "";
                        while (!comment.EndsWith("*/"))
                        {
                            comment += cur;
                            next();
                        }
                        if (oper == "/*")
                            return getToken();
                        return op(oper.Remove(oper.Length - 2));
                    }
                    #endregion
                }
                return op(oper);
                #endregion
            }
            else if (char.IsDigit(cur))
            {
                #region Numbers (Ints, Nums, and Periods)
                setPos();
                string text = "" + cur;
                next();
                while (char.IsDigit(cur))
                {
                    text += cur;
                    next();
                }
                if (cur == '.')
                {
                    TextPos periodPos = makePos(-1);
                    next();
                    if (!char.IsDigit(cur))
                    {
                        // TODO: text such as 1..5 (a range) will be parsed as '1' '.' '.' '5'
                        //       because this simply adds a '.' to the queue; it does not act
                        tokenQueue.Enqueue(op(".", periodPos));
                        //tokenQueue.Enqueue(getToken());
                    }
                    else
                    {
                        text += '.';
                        while (char.IsDigit(cur))
                        {
                            text += cur;
                            next();
                        }
                        return num(text);
                    }
                }
                return intnum(text);
                #endregion
            }
            else if (cur == '\'')
            {
                #region Single-Quoted String
                setPos();
                string text = "";
                next();
                while (cur != '\'' && cur != '\0')
                {
                    if (cur == '\\')
                    {
                        next();
                        switch (cur)
                        {
                            #region Escape Sequences
                            case 'n':
                                next();
                                text += '\n';
                                break;
                            case 't':
                                next();
                                text += '\t';
                                break;
                            case '\'':
                                next();
                                text += '\'';
                                break;
                            case '\"':
                                next();
                                text += '"';
                                break;
                            case '\\':
                                next();
                                text += '\\';
                                break;
                            #endregion
                            default:
                                throw new Exception("Invalid escape sequence '\\" + cur + "'.");
                        }
                    }
                    else
                    {
                        text += cur;
                        next();
                    }
                }
                if (cur == '\0')
                    throw new Exception("String not delimited; passed end of file.");
                next();
                return str("'" + text + "'", text);
                #endregion
            }
            else if (cur == '"')
            {
                #region Double-Quoted String
                setPos();
                string text = "";
                next();
                while (cur != '"' && cur != '\0')
                {
                    if (cur == '\\')
                    {
                        next();
                        switch (cur)
                        {
                            #region Escape Sequences
                            case 'n':
                                next();
                                text += '\n';
                                break;
                            case 't':
                                next();
                                text += '\t';
                                break;
                            case '\'':
                                next();
                                text += '\'';
                                break;
                            case '\"':
                                next();
                                text += '"';
                                break;
                            case '\\':
                                next();
                                text += '\\';
                                break;
                            #endregion
                            default:
                                throw new Exception("Invalid escape sequence '\\" + cur + "'.");
                        }
                    }
                    else
                    {
                        text += cur;
                        next();
                    }
                }
                if (cur == '\0')
                    throw new Exception("String not delimited; passed end of file.");
                next();
                return str("\"" + text + "\"", text);
                #endregion
            }
            else if (cur == ':')
            {
                #region Symbols
                setPos();
                string sym = "";
                next();
                if (identStart(cur))
                {
                    sym += cur;
                    next();
                    while (identChar(cur))
                    {
                        sym += cur;
                        next();
                    }
                    return symbol(sym);
                }
                else
                {
                    return op(":");
                    //throw new Exception("Expected identifier immediately following ':' in input.");
                }
                #endregion
            }
            //if (cur == '\0')
            //    throw new Exception("String not delimited; passed end of file.");
            //next();
            throw new Exception("Invalid character '" + cur + "' in input.");
        }

        [DebuggerNonUserCode]
        private Token ident(string name)
        {
            return new Token(TokenType.IDENT, name, getPos());
        }
        [DebuggerNonUserCode]
        private Token ident(string name, TextPos pos)
        {
            return new Token(TokenType.IDENT, name, pos);
        }

        [DebuggerNonUserCode]
        private Token op(string oper)
        {
            return new Token(TokenType.OPERATOR, oper, getPos());
        }
        [DebuggerNonUserCode]
        private Token op(string oper, TextPos pos)
        {
            return new Token(TokenType.OPERATOR, oper, pos);
        }

        [DebuggerNonUserCode]
        private Token groupsym(string group)
        {
            return new Token(TokenType.GROUPING, group, getPos());
        }

        [DebuggerNonUserCode]
        private Token str(string text, string s)
        {
            return new Token(TokenType.STRING, text, getPos(), s);
        }
        [DebuggerNonUserCode]
        private Token intnum(string text)
        {
            return new Token(TokenType.INTEGER, text, getPos(), long.Parse(text));
        }
        [DebuggerNonUserCode]
        private Token num(string text)
        {
            return new Token(TokenType.DOUBLE, text, getPos(), double.Parse(text));
        }
        [DebuggerNonUserCode]
        private Token symbol(string sym)
        {
            return new Token(TokenType.SYMBOL, ":" + sym, getPos(), sym);
        }

        [DebuggerNonUserCode]
        private Token semicolon()
        {
            return new Token(TokenType.SEMICOLON, ";", getPos());
        }

        //[DebuggerNonUserCode]
        //private Token newline()
        //{
        //    return new Token(TokenType.NEWLINE, "\n", getPos());
        //}
    }
}
